﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DynamicVariablesPostProcessor.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the DynamicVariablesPostProcessor type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Engine.Factory.Spring
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;

    using Collaboris.Saf.Engine.DynamicVariables;

    using global::Spring.Core;
    using global::Spring.Objects.Factory;
    using global::Spring.Objects.Factory.Config;

    /// <summary>
    /// Sotores the values of the variables and respective names
    /// </summary>
    public class VariablesDictionary : Dictionary<string, object>
    {
        #region Public Methods

        /// <summary>
        /// The convert.
        /// </summary>
        /// <returns>
        /// </returns>
        public NameValueCollection Convert()
        {
            var nvc = new NameValueCollection();

            foreach (string variableName in this.Keys)
            {
                nvc.Add(variableName, this[variableName].ToString());
            }

            return nvc;
        }

        #endregion
    }

    /// <summary>
    /// Spring post processors that parses all objects definitions and registers all
    /// objects publishing and subscribing to variables
    /// </summary>
    public class DynamicVariablesPostProcessor : IObjectFactoryPostProcessor, IOrdered
    {
        #region Constants and Fields

        /// <summary>
        /// The default placeholder prefix.
        /// </summary>
        private const string DefaultPublishingPlaceholderPrefix = "#{";

        /// <summary>
        /// The default placeholder suffix.
        /// </summary>
        private const string DefaultPublishingPlaceholderSuffix = "}";

        /// <summary>
        /// The default placeholder prefix.
        /// </summary>
        private const string DefaultSubscribingPlaceholderPrefix = "@{";

        /// <summary>
        /// The default placeholder suffix.
        /// </summary>
        private const string DefaultSubscribingPlaceholderSuffix = "}";

        // private readonly RegisterInstance registerInstanceHandler;

        /// <summary>
        /// The placeholder publishing prefix.
        /// </summary>
        private static string placeholderPublishingPrefix = DefaultPublishingPlaceholderPrefix;

        /// <summary>
        /// The placeholder publishing suffix.
        /// </summary>
        private static string placeholderPublishingSuffix = DefaultPublishingPlaceholderSuffix;

        /// <summary>
        /// The placeholder subscribing prefix.
        /// </summary>
        private static string placeholderSubscribingPrefix = DefaultSubscribingPlaceholderPrefix;

        /// <summary>
        /// The placeholder subscribing suffix.
        /// </summary>
        private static string placeholderSubscribingSuffix = DefaultSubscribingPlaceholderSuffix;

        /// <summary>
        /// The register publication.
        /// </summary>
        private PublicationHandler registerPublication;

        /// <summary>
        /// The register subscription.
        /// </summary>
        private SubscriptionHandler registerSubscription;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicVariablesPostProcessor"/> class.
        /// </summary>
        /// <param name="registerPublication">
        /// The register publication handler.
        /// </param>
        /// <param name="registerSubscription">
        /// The register subscription handler.
        /// </param>
        public DynamicVariablesPostProcessor(
            PublicationHandler registerPublication, SubscriptionHandler registerSubscription)
        {
            this.registerPublication = registerPublication;
            this.registerSubscription = registerSubscription;
        }

        #endregion

        #region Delegates

        /// <summary>
        /// The publication handler.
        /// </summary>
        /// <param name="variableName">
        /// The variable name.
        /// </param>
        /// <param name="publication">
        /// The publication.
        /// </param>
        public delegate void PublicationHandler(string variableName, ObjectPublication publication);

        /// <summary>
        /// The subscription handler.
        /// </summary>
        /// <param name="variableName">
        /// The variable name.
        /// </param>
        /// <param name="subscription">
        /// The subscription.
        /// </param>
        public delegate void SubscriptionHandler(string variableName, VariableSubscription subscription);

        #endregion

        // <summary>
        // Contains the publishing variables and respective values
        /// </summary>
        // private VariablesDictionary publishedVariables = new VariablesDictionary();
        #region Properties

        /// <summary>
        /// Gets or sets the placeholder publishing prefix.
        /// </summary>
        /// <value>The placeholder publishing prefix.</value>
        public static string PlaceholderPublishingPrefix
        {
            get
            {
                return placeholderPublishingPrefix;
            }

            set
            {
                placeholderPublishingPrefix = value;
            }
        }

        /// <summary>
        /// Gets or sets the placeholder publishing suffix.
        /// </summary>
        /// <value>The placeholder publishing suffix.</value>
        public static string PlaceholderPublishingSuffix
        {
            get
            {
                return placeholderPublishingSuffix;
            }

            set
            {
                placeholderPublishingSuffix = value;
            }
        }

        /// <summary>
        /// Gets or sets the placeholder subscribing prefix.
        /// </summary>
        /// <value>The placeholder subscribing prefix.</value>
        public static string PlaceholderSubscribingPrefix
        {
            get
            {
                return placeholderSubscribingPrefix;
            }

            set
            {
                placeholderSubscribingPrefix = value;
            }
        }

        /// <summary>
        /// Gets or sets the placeholder subscribing suffix.
        /// </summary>
        /// <value>The placeholder subscribing suffix.</value>
        public static string PlaceholderSubscribingSuffix
        {
            get
            {
                return placeholderSubscribingSuffix;
            }

            set
            {
                placeholderSubscribingSuffix = value;
            }
        }

        #endregion

        #region Implemented Interfaces

        #region IObjectFactoryPostProcessor

        /// <summary>
        /// Modify the application context's internal object factory after its
        /// standard initialization.
        /// </summary>
        /// <remarks>
        /// <p>All object definitions will have been loaded, but no objects will have
        /// been instantiated yet. This allows for overriding or adding properties
        /// even to eager-initializing objects.
        /// </p>
        /// </remarks>
        /// <param name="factory">
        /// The object factory used by the application context.
        /// </param>
        /// <exception cref="T:Spring.Objects.ObjectsException">
        /// In case of errors.
        /// </exception>
        public void PostProcessObjectFactory(IConfigurableListableObjectFactory factory)
        {
            var properties = new NameValueCollection();

            // PlaceholderResolveHandlerAdapter resolveAdapter = new PlaceholderResolveHandlerAdapter(this, properties);
            var visitor = new SafObjectDefinitionVisitorEx(this.DynamicAssignmentsResolveHandler);

            string[] objectDefinitionNames = factory.GetObjectDefinitionNames();
            for (int i = 0; i < objectDefinitionNames.Length; ++i)
            {
                string name = objectDefinitionNames[i];

                IObjectDefinition definition = factory.GetObjectDefinition(name);
                try
                {
                    visitor.VisitObjectDefinition(definition, name);
                }
                catch (ObjectDefinitionStoreException ex)
                {
                    throw new ObjectDefinitionStoreException(definition.ResourceDescription, name, ex.Message);
                }
            }
        }

        #endregion

        #region IOrdered

        /// <summary>
        /// Return the order value of this object, where a higher value means greater in terms of sorting.
        /// </summary>
        /// <remarks>
        /// <p>Normally starting with 0 or 1, with <see cref="F:System.Int32.MaxValue"/> indicating
        /// greatest. Same order values will result in arbitrary positions for the affected
        /// objects.
        /// </p>
        /// <p>Higher value can be interpreted as lower priority, consequently the first object
        /// has highest priority.
        /// </p>
        /// </remarks>
        /// <returns>
        /// The order value.
        /// </returns>
        public int Order
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #endregion

        #region Helper Methods

        /// <summary>
        /// Dynamics the assignments resolve handler.
        /// </summary>
        /// <param name="rawPropValue">
        /// The raw prop value.
        /// </param>
        /// <param name="propName">
        /// Name of the prop.
        /// </param>
        /// <param name="definitionName">
        /// Name of the definition.
        /// </param>
        /// <returns>
        /// The dynamic assignments resolve handler.
        /// </returns>
        private bool DynamicAssignmentsResolveHandler(string rawPropValue, string propName, string definitionName)
        {
            // Register publishing properties
            string publishingPropertyValue = this.GetVariableName(
                rawPropValue, PlaceholderPublishingPrefix, PlaceholderPublishingSuffix);
            if (!String.IsNullOrEmpty(publishingPropertyValue))
            {
                this.RegisterPublication(publishingPropertyValue, propName, definitionName);

                return true;
            }

            // Register subscription properties
            string subscribedPropertyName = this.GetVariableName(
                rawPropValue, PlaceholderSubscribingPrefix, PlaceholderSubscribingSuffix);
            if (!String.IsNullOrEmpty(subscribedPropertyName))
            {
                this.RegisterSubscription(subscribedPropertyName, rawPropValue, propName, definitionName);

                return true;
            }

            return false;
        }

        /// <summary>
        /// Extracts the variable name from the string
        /// </summary>
        /// <param name="strVal">
        /// The str val.
        /// </param>
        /// <param name="placeholderPrefix">
        /// The placeholder Prefix.
        /// </param>
        /// <param name="placeholderSuffix">
        /// The placeholder Suffix.
        /// </param>
        /// <returns>
        /// The get variable name.
        /// </returns>
        private string GetVariableName(string strVal, string placeholderPrefix, string placeholderSuffix)
        {
            int startIndex = strVal.IndexOf(placeholderPrefix);
            while (startIndex != -1)
            {
                int endIndex = strVal.IndexOf(placeholderSuffix, startIndex + placeholderPrefix.Length);
                if (endIndex != -1)
                {
                    int pos = startIndex + placeholderPrefix.Length;
                    string placeholder = strVal.Substring(pos, endIndex - pos);
                    return placeholder;
                }
            }

            return null;
        }

        /// <summary>
        /// Registers a new published variable
        /// </summary>
        /// <param name="propValue">
        /// The prop Value.
        /// </param>
        /// <param name="propName">
        /// The prop name.
        /// </param>
        /// <param name="definitionName">
        /// The definition name.
        /// </param>
        private void RegisterPublication(string propValue, string propName, string definitionName)
        {
            if (this.registerPublication != null)
            {
                this.registerPublication(definitionName, new ObjectPublication(propValue, propName, null, null));
            }
        }

        /// <summary>
        /// Registers the subscription.
        /// </summary>
        /// <param name="subscribedPropertyName">
        /// Name of the subscribed property.
        /// </param>
        /// <param name="rawPropertyValue">
        /// The raw property value.
        /// </param>
        /// <param name="propertyName">
        /// Name of the property.
        /// </param>
        /// <param name="objectName">
        /// Name of the object.
        /// </param>
        private void RegisterSubscription(
            string subscribedPropertyName, string rawPropertyValue, string propertyName, string objectName)
        {
            if (this.registerSubscription != null)
            {
                this.registerSubscription(
                    subscribedPropertyName, new VariableSubscription(objectName, propertyName, rawPropertyValue, null));
            }
        }

        #endregion
    }
}